﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;

using Point = System.Drawing.Point;
using Rectangle = System.Drawing.Rectangle;
using Color = Microsoft.Xna.Framework.Color;

namespace LevelEditor
{
    public partial class HeightmapEditor : Form
    {
        public HeightMap heightmap;
        private RenderTarget2D otherRenderTgt;

        /// <summary>
        /// The ammount of time remaing that editing will be disabled
        /// </summary>
        private TimeSpan editDisabledTime;

        /// <summary>
        /// Gets or sets a  value indicating that editing of the height map is currently disabled
        /// </summary>
        private bool disableEdit
        {
            get { return editDisabledTime.Ticks > 0; }

            set { editDisabledTime = value ? TimeSpan.FromMilliseconds(500) : TimeSpan.Zero; }
        }

        private ToolType CurrentTool
        {
            get
            {
                if (increaseHeightButton.Checked)
                    return ToolType.IncreeseHeight;
                else if (decreaceHeightButton.Checked)
                    return ToolType.DecreaseHeight;
                else if (smoothButton.Checked)
                    return ToolType.Smooth;
                else if (levelButton.Checked)
                    return ToolType.Level;

                return ToolType.Level;
            }
        }

        /// <summary>
        /// A collection of all the tool buttons on the tool bar
        /// </summary>
        private List<ToolStripButton> ToolButtons = new List<ToolStripButton>();

        /// <summary>
        /// The radius of the brush
        /// </summary>
        private int brushRadius
        {
            get { return brushWidthValue.Value; }
        }

        /// <summary>
        /// The hardness of the brush
        /// </summary>
        private int brushHardness
        {
            get { return hardnessTrackbar.Value; }
        }

        /// <summary>
        /// The value to paint on the heightmap
        /// </summary>
        public float paintValue
        {
            get { return (float)brushPaintValue.Value / 100.0f; }
        }

        private VertexPositionColor[] circleVerticies;

        private Effect shader;

        ContentManager content;

        MaterialEditor.ContentBuilder builder;

        MaterialEditor.ServiceContainer services;

        private BlendState decreaseHeightBlendState;

        /// <summary>
        /// Blend state used for bluring height values
        /// </summary>
        private BlendState blurBlendState;

        public HeightmapEditor()
        {
            InitializeComponent();

            ToolButtons.Add(increaseHeightButton);
            ToolButtons.Add(decreaceHeightButton);
            ToolButtons.Add(smoothButton);
            ToolButtons.Add(levelButton);

            // inilitize label values
            radiusValueLabel.Text = brushRadius.ToString();
            hardnessLabel.Text = brushHardness.ToString() + "%";

            decreaseHeightBlendState = new BlendState();
            decreaseHeightBlendState.AlphaSourceBlend = Blend.One;
            decreaseHeightBlendState.AlphaDestinationBlend = Blend.One;
            decreaseHeightBlendState.AlphaBlendFunction = BlendFunction.ReverseSubtract;

            blurBlendState = new BlendState();
            blurBlendState.AlphaSourceBlend = Blend.One;
            blurBlendState.AlphaDestinationBlend = Blend.Zero;
            blurBlendState.AlphaBlendFunction = BlendFunction.Subtract;

            heightmap = new HeightMap(100, 100, 0, 1, MainMenu.instance.graphics.device);
            otherRenderTgt = new RenderTarget2D(MainMenu.instance.graphics.device, 2000, 2000, false, SurfaceFormat.Color, DepthFormat.Depth16, 0, RenderTargetUsage.PreserveContents);

            // create circle vertecies
            List<VertexPositionColor> circleVerts = new List<VertexPositionColor>();
            for (int i = -180; i < 180; i++)
            {
                float angle = MathHelper.ToRadians(i);
                Vector3 point = new Vector3((float)Math.Cos(angle), (float)Math.Sin(angle), 0);

                circleVerts.Add(new VertexPositionColor(point, Color.Lime));
            }
            circleVerticies = circleVerts.ToArray();


            // create xna content
            services = new MaterialEditor.ServiceContainer();
            services.AddService<IGraphicsDeviceService>(new GraphicsDeviceService(MainMenu.instance.graphics.device));
            builder = new MaterialEditor.ContentBuilder();
            content = new ContentManager(services, builder.OutputDirectory);

            // compile heightmap effect
            builder.Add(Environment.CurrentDirectory + "\\Content\\effects\\Heightmap.fx", "Heightmap", "EffectImporter", "EffectProcessor");
            string err_msg = builder.Build();
            if (!string.IsNullOrEmpty(err_msg))
                MessageBox.Show(err_msg);
            else
                shader = content.Load<Effect>("Heightmap");

            MainMenu.instance.viewWindow1.MouseMove += viewWindow1_MouseMove;
            MainMenu.instance.viewWindow1.MouseDown += viewWindow1_MouseDown;
        }

        void viewWindow1_MouseMove(object sender, MouseEventArgs e)
        {
            viewWindow1_MouseDown(sender, e);
        }

        private void viewWindow1_MouseDown(object sender, MouseEventArgs e)
        {
            if (!Visible)
                return;

            ViewWindow viewWindow1 = MainMenu.instance.viewWindow1;
            TabControl tabControl1 = MainMenu.instance.tabControl1;
            TabPage terrainTabPage = MainMenu.instance.terrainTabPage;

            if (e.Button == System.Windows.Forms.MouseButtons.Right && !viewWindow1.Focused)
                viewWindow1.Focus();

            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                Terrain terrain = viewWindow1.terrain;

                System.Drawing.Point cursorPosition = MainMenu.instance.viewWindow1.PointToClient(System.Windows.Forms.Cursor.Position);
                Ray cameraRay = Camera.instance.ComputeRay(MainMenu.instance.viewWindow1.viewport, cursorPosition.X, cursorPosition.Y);
                Plane p = new Plane(Vector3.Up, 0);
                float? intersects = cameraRay.Intersects(p);
                if (intersects != null)
                {
                    Vector3 intersectPoint = cameraRay.Position + cameraRay.Direction * (float)intersects;
                    PaintValue(paintValue, new MouseEventArgs(System.Windows.Forms.MouseButtons.Left, 0, (int)intersectPoint.X, (int)intersectPoint.Z, 0), "renderValue");
                }
            }
        }

        private void levelDimensionsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            throw new NotImplementedException();
        }

        float distance(Point a, Point b)
        {
            return (float)Math.Sqrt(Math.Pow(b.X - a.X, 2) + Math.Pow(b.Y - a.Y, 2));
        }

        float clamp(float value, float min, float max)
        {
            if (value > max)
                return max;
            if (value < min)
                return min;

            return value;
        }

        private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
        {
            if (disableEdit)
                return;
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                if (CurrentTool == ToolType.IncreeseHeight)
                    PaintValue(paintValue, e, "renderValue");
                else if (CurrentTool == ToolType.DecreaseHeight)
                    PaintValue(-paintValue, e, "renderValue");
                else if (CurrentTool == ToolType.Smooth)
                {

                }
            }
        }

        ScreenQuad quad = new ScreenQuad();

        public void PaintValue(float value, MouseEventArgs e, string techniqueName)
        {
            Graphics g = MainMenu.instance.graphics;
            if (CurrentTool != ToolType.Smooth)
            {
                // set render states
                shader.CurrentTechnique = shader.Techniques[techniqueName];
                g.device.DepthStencilState = DepthStencilState.Default;
                g.device.SamplerStates[0] = SamplerState.PointClamp;

                if (CurrentTool == ToolType.IncreeseHeight)
                    g.device.BlendState = BlendState.Additive;
                else if (CurrentTool == ToolType.DecreaseHeight || CurrentTool == ToolType.Smooth)
                    g.device.BlendState = decreaseHeightBlendState;

                // set value render variables
                shader.Parameters["mouseCoords"].SetValue(new Vector2(e.Location.X, e.Location.Y));
                shader.Parameters["textureSize"].SetValue(new Vector2(heightmap.MapWidth, heightmap.MapHeight));
                shader.Parameters["value"].SetValue(value);
                shader.Parameters["range"].SetValue((float)brushRadius);
                g.device.Textures[0] = heightmap.data;
                g.effect = shader;

                // update heightmap
                g.device.SetRenderTarget(heightmap.data);
                shader.CurrentTechnique.Passes[0].Apply();
                quad.Draw();

                g.device.SetRenderTarget(null);
                g.device.BlendState = BlendState.Opaque;
            }
            else
            {
                shader.CurrentTechnique = shader.Techniques["renderBlur"];

                shader.Parameters["mouseCoords"].SetValue(new Vector2(e.Location.X, e.Location.Y));
                shader.Parameters["range"].SetValue((float)brushRadius);
                shader.Parameters["textureSize"].SetValue(new Vector2(heightmap.MapWidth, heightmap.MapHeight));

                shader.GraphicsDevice.BlendState = BlendState.Additive;
                shader.CurrentTechnique.Passes[0].Apply();
                shader.GraphicsDevice.Textures[0] = heightmap.data;
                g.device.SetRenderTarget(otherRenderTgt);
                g.device.Clear(Color.Transparent);
                quad.Draw();

                shader.GraphicsDevice.BlendState = BlendState.NonPremultiplied;
                EffectManager.BasicShader.CurrentTechnique.Passes[0].Apply();
                shader.GraphicsDevice.BlendState = blurBlendState;
                g.device.SetRenderTarget(heightmap.data);
                g.device.Textures[0] = otherRenderTgt;
                g.device.Clear(Color.Transparent);
                quad.Draw();

                g.device.SetRenderTarget(null);
            }
        }

        /// <summary>
        /// Updates the editor by one frame
        /// </summary>
        /// <param name="dt">the ammount of time, in seconds, since the last update</param>
        public void Tick(float dt)
        {
            if (disableEdit)
                editDisabledTime -= TimeSpan.FromSeconds((double)dt);
        }
        
        private enum ToolType
        {
            IncreeseHeight,
            DecreaseHeight,
            Smooth,
            Level,
        }

        private void DeselectToolButtons()
        {
            foreach (ToolStripButton b in ToolButtons)
                b.Checked = false;
        }

        private void increaseHeightButton_Click(object sender, EventArgs e)
        {
            DeselectToolButtons();
            increaseHeightButton.Checked = true;
        }

        private void decreaceHeightButton_Click(object sender, EventArgs e)
        {
            DeselectToolButtons();
            decreaceHeightButton.Checked = true;
        }

        private void smoothButton_Click(object sender, EventArgs e)
        {
            DeselectToolButtons();
            smoothButton.Checked = true;
        }

        private void levelButton_Click(object sender, EventArgs e)
        {
            DeselectToolButtons();
            levelButton.Checked = true;
        }

        private void importToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // load image as heightmap
            OpenFileDialog dialog = new OpenFileDialog();
            dialog.Filter = "Png files | *.png; | All Files | *.*;";
            DialogResult result = dialog.ShowDialog();

            if (result == System.Windows.Forms.DialogResult.OK)
            {
                // disable editing for half a second
                disableEdit = true;

                // load in texture and render it to the heightmap
                Texture2D tex = Texture2D.FromStream(MainMenu.instance.graphics.device, new System.IO.FileStream(dialog.FileName, System.IO.FileMode.Open));

                heightmap.data = new RenderTarget2D(MainMenu.instance.graphics.device, tex.Width, tex.Height);

                shader.CurrentTechnique = shader.Techniques["normal"];
                MainMenu.instance.graphics.device.SetRenderTarget(heightmap.data);
                MainMenu.instance.graphics.device.Clear(Color.DarkGray);
                MainMenu.instance.graphics.effect = shader;
                MainMenu.instance.graphics.DrawTexturedRect(new Rectangle(), tex, null);
                MainMenu.instance.graphics.effect = null;
                MainMenu.instance.graphics.device.SetRenderTarget(null);
                MainMenu.instance.graphics.device.Clear(Color.CornflowerBlue);
            }

        }

        public void Draw()
        {
            Camera.instance.FormatEffect(EffectManager.FlatShader);
            Ray cameraRay = Camera.instance.ComputeRay(MainMenu.instance.graphics.device.Viewport, Cursor.Position.X, Cursor.Position.Y);
            Plane p = new Plane(Vector3.Up, 0.0f);
            float? intersectDistance = cameraRay.Intersects(p);
            if (intersectDistance != null)
            {
                Vector3 intersectPoint = cameraRay.Position + cameraRay.Direction *  (float)intersectDistance;
                EffectManager.FlatShader.CurrentTechnique.Passes[0].Apply();
                EffectManager.FlatShader.Parameters["World"].SetValue(Matrix.CreateTranslation(intersectPoint));
                shader.GraphicsDevice.BlendState = BlendState.Opaque;
                shader.GraphicsDevice.DrawUserPrimitives(PrimitiveType.LineStrip, circleVerticies, 0, circleVerticies.Length / 2);
            }
        }

        private void applyButton_Click(object sender, EventArgs e)
        {
        }

        private void newMenuStripItem_Click(object sender, EventArgs e)
        {
            ResizeLevel newResize = new ResizeLevel(100, 100);
            newResize.FormClosing += newResize_FormClosing;
            newResize.Show(this);
        }

        void newResize_FormClosing(object sender, FormClosingEventArgs e)
        {
            ResizeLevel window = sender as ResizeLevel;
            HeightMap map = MainMenu.instance.viewWindow1.terrain.HeightMap;
            MainMenu.instance.viewWindow1.terrain.HeightMap = new HeightMap((int)window.widthValue.Value, (int)window.heightValue.Value, map.lowest, map.heighest, MainMenu.instance.graphics.device);
        }

        private void panel1_SizeChanged(object sender, EventArgs e)
        {

        }

        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Hide();
        }

        private void HeightmapEditor_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = true;
            this.Hide();
        }

        private void brushWidthValue_ValueChanged(object sender, EventArgs e)
        {
            radiusValueLabel.Text = brushRadius.ToString();
        }

        private void hardnessTrackbar_ValueChanged(object sender, EventArgs e)
        {
            hardnessLabel.Text = brushHardness.ToString() + "%";
        }

        private void brushPaintValue_Scroll(object sender, EventArgs e)
        {
            paintValueLabel.Text = brushPaintValue.Value.ToString();
        }
    }
}
